package com.qf.cx.simplenms.readergroup.impl;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.qf.cx.simplenms.common.utils.SetUtils;
import com.qf.cx.simplenms.reader.api.Reader;
import com.qf.cx.simplenms.reader.api.ReaderManager;
import com.qf.cx.simplenms.reader.model.AntennaModel;
import com.qf.cx.simplenms.reader.tag.AntennaTag;
import com.qf.cx.simplenms.reader.tag.ReaderTags;
import com.qf.cx.simplenms.readergroup.api.ReaderGroup;
import com.qf.cx.simplenms.readergroup.model.ReaderGroupModel;
import com.qf.cx.simplenms.readergroup.spi.ReaderGroupSpi;
import com.qf.cx.simplenms.readergroup.tag.ReaderGroupTag;
import com.qf.cx.simplenms.readergroup.tag.ReaderGroupTags;
import com.qf.cx.simplenms.tagmemory.model.TagMemoryModel;

public class ReaderGroupImpl extends Observable implements ReaderGroup, Observer, ReaderGroupSpi {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	private final AtomicBoolean started = new AtomicBoolean(false);
	private final ReaderGroupModel readerGroupModel;
	private final Map<String, Reader> name2Reader = new ConcurrentHashMap<>();
	private final ReaderManager readerManager;

	/**
	 * 缓存每个观察者所配置的标签区信息
	 */
	private final Map<Observer, CopyOnWriteArraySet<TagMemoryModel>> observer2TagMemories = new ConcurrentHashMap<>();

	public ReaderGroupImpl(ReaderGroupModel readerGroupModel, ReaderManager readerManager) {
		this.readerGroupModel = readerGroupModel;
		this.readerManager = readerManager;
		for (AntennaModel ant : readerGroupModel.getAntennas()) {
			String readerName = ant.getReaderName();
			// 已经持有该reader
			if (name2Reader.containsKey(readerName)) {
				continue;
			}
			Reader reader = this.readerManager.getReader(readerName);
			if (reader != null) {
				name2Reader.put(readerName, reader);
			}
		}
	}

	@Override
	public void addTagMemories(Observer observer, Set<TagMemoryModel> news) {
		if (news.isEmpty()) {
			return;
		}
		news = new HashSet<>(news);
		logger.debug("reader group '{}' add tms {}", getReaderGroupName(), news);
		Set<TagMemoryModel> uniqueTagMemories = generateUniqueTagMemories();
		observer2TagMemories.putIfAbsent(observer, new CopyOnWriteArraySet<>());
		Set<TagMemoryModel> olds = observer2TagMemories.get(observer);
		olds.addAll(news);
		news.removeAll(uniqueTagMemories);
		if (started.get()) {
			for (Reader t : name2Reader.values()) {
				t.addTagMemories(this, news);
				startReader(t);
			}
		}
	}

	private String getReaderGroupName() {
		return readerGroupModel.getName();
	}

	@Override
	public void deleteTagMemories(Observer observer, Set<TagMemoryModel> deletes) {
		if (deletes.isEmpty()) {
			return;
		}
		deletes = new HashSet<>(deletes);
		logger.debug("reader group '{}' delete tms {}", getReaderGroupName(), deletes);

		observer2TagMemories.putIfAbsent(observer, new CopyOnWriteArraySet<>());
		Set<TagMemoryModel> olds = observer2TagMemories.get(observer);
		olds.removeAll(deletes);
		deletes.removeAll(generateUniqueTagMemories());

		if (started.get()) {
			for (Reader r : name2Reader.values()) {
				r.deleteTagMemories(this, deletes);
			}
		}
	}

	@Override
	public void start(Observer observer) {
		super.addObserver(observer);
		if (!started.compareAndSet(false, true)) {
			logger.debug("reader group '{}' already started", getReaderGroupName());
			return;
		}
		logger.info("reader group '{}' starting", getReaderGroupName());
		for (Reader t : name2Reader.values()) {
			t.addTagMemories(ReaderGroupImpl.this, generateUniqueTagMemories());
			startReader(t);
		}

		logger.info("reader group '{}' started", getReaderGroupName());
		if (logger.isDebugEnabled()) {
			logger.debug("rap:");
			readerGroupModel.getAntennas().forEach(t -> logger.debug(" " + t));
			logger.debug("observer & tm");
			observer2TagMemories.entrySet().forEach(t -> {
				logger.debug(" observer:");
				logger.debug("  " + t.getKey());
				logger.debug(" tm:");
				logger.debug("  " + t.getValue());
			});
		}
	}

	private Set<TagMemoryModel> generateUniqueTagMemories() {
		// 保证标签区区只下发一次
		Set<TagMemoryModel> set = new HashSet<>();
		this.observer2TagMemories.values().forEach(t -> set.addAll(t));
		return set;
	}

	@Override
	public void stop(Observer observer) {
		observer2TagMemories.remove(observer);
		super.deleteObserver(observer);
		if (countObservers() == 0 && started.compareAndSet(true, false)) {
			stop0();
		}
	}

	@Override
	public void forceStop() {
		stop0();
	}

	private void stop0() {
		logger.info("reader group '{}' stoping", getReaderGroupName());

		for (Reader t : name2Reader.values()) {
			t.deleteTagMemories(ReaderGroupImpl.this, generateUniqueTagMemories());
			stopReader(t);
		}

		deleteObservers();

		logger.info("reader group '{}' stopped", getReaderGroupName());
	}

	@Override
	public void update(Observable o, Object arg) {

		if (!started.get()) {
			return;
		}
		if (!(arg instanceof ReaderTags)) {
			logger.error("can not handle type {}", arg.getClass());
			return;
		}

		ReaderTags received = (ReaderTags) arg;
		ReaderGroupTags readerGroupTags = new ReaderGroupTags();
		readerGroupTags.setReaderGroupName(getReaderGroupName());

		for (AntennaTag antennaTag : received.getTags()) {
			logger.debug("receive tags {}", antennaTag);
			if (this.readerGroupModel.contains(antennaTag.getAntenna())) {
				ReaderGroupTag e = new ReaderGroupTag();
				e.setAntenna(antennaTag.getAntenna());
				e.setTag(antennaTag.getTag());
				e.mergeStatistics(Collections.singletonMap(antennaTag.getAntenna(), antennaTag.getStatistic()));
				readerGroupTags.add(e);
			} else {
				logger.debug("drop tags {}", antennaTag);
			}
		}

		setChanged();
		logger.debug("send tags " + readerGroupTags.getTags());
		notifyObservers(readerGroupTags);

	}

	@Override
	public void updateReaderGroupModel(ReaderGroupModel readerGroupModel) {
		synchronized (this.readerGroupModel) {
			Set<AntennaModel> news = readerGroupModel.getAntennas();
			Set<AntennaModel> olds = this.readerGroupModel.getAntennas();

			Set<AntennaModel> dels = SetUtils.getComplementary(olds, news);
			deleteAntennas(dels);

			Set<AntennaModel> adds = SetUtils.getComplementary(news, olds);
			addAntennas(adds);
		}

	}

	private void deleteAntennas(Set<AntennaModel> dels) {
		if (dels.isEmpty()) {
			return;
		}
		logger.debug("reader group '{}' delete ants {}", getReaderGroupName(), dels);
		this.readerGroupModel.deleteAntennas(dels);

		Set<Reader> idleReaders = new HashSet<>();
		for (AntennaModel ant : dels) {
			// 仍然持有该reader，无需清理
			String readerName = ant.getReaderName();
			if (readerGroupModel.containsReader(readerName)) {
				continue;
			}
			Reader reader = name2Reader.remove(readerName);
			if (reader != null) {
				idleReaders.add(reader);
			}
		}
		if (started.get()) {
			for (Reader t : idleReaders) {
				t.deleteTagMemories(ReaderGroupImpl.this, generateUniqueTagMemories());
				stopReader(t);
			}
		}
	}

	private void addAntennas(Set<AntennaModel> adds) {
		if (adds.isEmpty()) {
			return;
		}
		logger.debug("reader group '{}' add ants {}", getReaderGroupName(), adds);
		this.readerGroupModel.addAntennas(adds);
		Set<Reader> newReaders = new HashSet<>();

		for (AntennaModel ant : adds) {
			String readerName = ant.getReaderName();
			// 已经持有该reader
			if (name2Reader.containsKey(readerName)) {
				continue;
			}
			Reader reader = readerManager.getReader(readerName);
			if (reader != null) {
				name2Reader.put(readerName, reader);
				newReaders.add(reader);
			}
		}

		if (started.get()) {
			for (Reader t : newReaders) {
				t.addTagMemories(ReaderGroupImpl.this, generateUniqueTagMemories());
				startReader(t);
			}
		}
	}

	private void startReader(Reader t) {
		t.start(this);
	}

	private void stopReader(Reader t) {
		t.stop(this);
	}


	@Override
	public String toString() {
		return "ReaderGroupImpl [started=" + started + ", readerGroupModel=" + readerGroupModel
				+ ", observer2TagMemories=" + observer2TagMemories;
	}

	@Override
	public String dump() {
		StringBuilder sb = new StringBuilder();
		sb.append("name=").append(readerGroupModel.getName());
		sb.append(", started=").append(started);
		sb.append("</br>Antennas:[");
		for (AntennaModel e : readerGroupModel.getAntennas()) {
			sb.append("</br>[");
			sb.append("readerName=").append(e.getReaderName());
			sb.append(", antennaNo=").append(e.getAntennaNo());
			sb.append(", protocol=").append(e.getProtocol());
			sb.append("]");
		}
		sb.append("</br>]");
		sb.append("</br>Tag Memories:[");
		for (TagMemoryModel e : generateUniqueTagMemories()) {
			sb.append("</br>[");
			sb.append(e);
			sb.append("]");
		}
		sb.append("</br>]</br>");

		return sb.toString();
	}

	@Override
	public String getName() {
		return readerGroupModel.getName();
	}

}
